package com.zhang.novel.service.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zhang.novel.core.cache.CacheKey;
import com.zhang.novel.core.cache.CacheService;
import com.zhang.novel.core.enums.ResponseStatus;
import com.zhang.novel.core.exception.BusinessException;
import com.zhang.novel.core.utils.BeanUtil;
import com.zhang.novel.core.utils.Constants;
import com.zhang.novel.core.utils.IdWorker;
import com.zhang.novel.entity.*;
import com.zhang.novel.entity.Book;
import com.zhang.novel.mapper.*;
import com.zhang.novel.search.BookSP;
import com.zhang.novel.service.BookService;
import com.zhang.novel.service.FileService;
import com.zhang.novel.service.SearchService;
import com.zhang.novel.vo.BookCommentVO;
import com.zhang.novel.vo.BookSettingVO;
import com.zhang.novel.vo.BookVO;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SortSpecification;
import org.mybatis.dynamic.sql.render.RenderingStrategies;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.orderbyhelper.OrderByHelper;

import static com.zhang.novel.mapper.BookCategoryDynamicSqlSupport.bookCategory;
import static com.zhang.novel.mapper.BookCategoryDynamicSqlSupport.name;
import static com.zhang.novel.mapper.BookCategoryDynamicSqlSupport.sort;
import static com.zhang.novel.mapper.BookCommentDynamicSqlSupport.bookComment;
import static com.zhang.novel.mapper.BookContentDynamicSqlSupport.bookContent;
import static com.zhang.novel.mapper.BookContentDynamicSqlSupport.indexId;
import static com.zhang.novel.mapper.BookDynamicSqlSupport.*;
import static com.zhang.novel.mapper.BookIndexDynamicSqlSupport.bookId;
import static com.zhang.novel.mapper.BookIndexDynamicSqlSupport.bookIndex;
import static org.mybatis.dynamic.sql.SqlBuilder.*;
import static org.mybatis.dynamic.sql.select.SelectDSL.select;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class BookSeviceImpl implements BookService {

    private final CacheService cacheService;
    private final FrontBookMapper bookMapper;
    private final FrontBookSettingMapper bookSettingMapper;
    private final BookCategoryMapper bookCategoryMapper;
    private final BookContentMapper bookContentMapper;
    private final BookIndexMapper bookIndexMapper;
    private final FrontBookCommentMapper bookCommentMapper;
    private final BookAuthorMapper bookAuthorMapper;
    private final SearchService searchService;
    private final FileService fileService;
    @Value("${spring.elasticsearch.enable}")
    private Integer esEnable;

    @Value("${cache.type}")
    private String type;

    /**
     * 本地图片保存路径
     */
    @Value("${pic.save.path}")
    private String picSavePath;

    /**
     * 查询首页小说，设置列表数据
     *
     * @return
     */
    @Override
    @SneakyThrows
    public Map<Byte, List<BookSettingVO>> listBookSettingVO() {
        System.out.println(type);
        String result = cacheService.get(CacheKey.INDEX_BOOK_SETTINGS_KEY);
        if (result == null || result.length() < Constants.OBJECT_JSON_CACHE_EXIST_LENGTH) {
            List<BookSettingVO> list = bookSettingMapper.listVO();
            if (list.size() == 0) {
                //如果首页小说没有被设置，则初始化首页小说设置
                list = initIndexBookSetting();
            }
            result = new ObjectMapper().writeValueAsString(list.stream().collect(Collectors.groupingBy(BookSettingVO::getType)));
            cacheService.set(CacheKey.INDEX_BOOK_SETTINGS_KEY, result);
        }
        return new ObjectMapper().readValue(result, Map.class);
    }

    /**
     * 初始化首页小说设置
     */
    private List<BookSettingVO> initIndexBookSetting() {
        Date currentDate = new Date();
        List<Book> books = bookMapper.selectIdsByScoreAndRandom(Constants.INDEX_BOOK_SETTING_NUM);
        if (books.size() == Constants.INDEX_BOOK_SETTING_NUM) {
            List<BookSetting> bookSettingList = new ArrayList<>(Constants.INDEX_BOOK_SETTING_NUM);
            List<BookSettingVO> bookSettingVOList = new ArrayList<>(Constants.INDEX_BOOK_SETTING_NUM);
            for (int i = 0; i < books.size(); i++) {
                Book book = books.get(i);
                byte type;
                if (i < 4) {
                    type = 0;
                } else if (i < 14) {
                    type = 1;
                } else if (i < 20) {
                    type = 2;
                } else if (i < 26) {
                    type = 3;
                } else {
                    type = 4;
                }
                BookSettingVO bookSettingVO = new BookSettingVO();
                BookSetting bookSetting = new BookSetting();
                bookSetting.setType(type);
                bookSetting.setSort((byte) i);
                bookSetting.setBookId(book.getId());
                bookSetting.setCreateTime(currentDate);
                bookSetting.setUpdateTime(currentDate);
                bookSettingList.add(bookSetting);

                BeanUtils.copyProperties(book, bookSettingVO);
                BeanUtils.copyProperties(bookSetting, bookSettingVO);
                bookSettingVOList.add(bookSettingVO);
            }

            bookSettingMapper.insertMultiple(bookSettingList);

            return bookSettingVOList;
        }
        return new ArrayList<>(0);
    }

    @Override
    public List<BookVO> listClickRank() {
        // 先从缓存中取
        List<BookVO> result = (List<BookVO>) cacheService.getObject(CacheKey.INDEX_CLICK_BANK_BOOK_KEY);
//        List<BookVO> result = null;
        // 判断缓存中是否存在数据
        if (null == result || result.isEmpty()) {
            // 不存在缓存，从数据库中获取
            List<Book> bookPOList = listRank((byte) 0, 23);
            result = BeanUtil.copyList(bookPOList, BookVO.class);
            //存入缓存
            cacheService.setObject(CacheKey.INDEX_CLICK_BANK_BOOK_KEY, result, 3600);
        }
        return result;
    }

    @Override
    public List<BookVO> listNewRank() {
        // 从缓存中获取
        List<BookVO> result = (List<BookVO>) cacheService.getObject(CacheKey.INDEX_NEW_BOOK_KEY);
        if (null == result || result.isEmpty()) {
            List<Book> books = listRank((byte) 1, 23);
            result = BeanUtil.copyList(books, BookVO.class);

            cacheService.setObject(CacheKey.INDEX_NEW_BOOK_KEY, result, 3600);
        }

        return result;
    }

    @Override
    public List<BookVO> listUpdateRank() {
        // 从缓存中获取
        List<BookVO> result = (List<BookVO>) cacheService.getObject(CacheKey.INDEX_UPDATE_BOOK_KEY);
        if (null == result || result.isEmpty()) {
            List<Book> books = listRank((byte) 2, 23);
            result = BeanUtil.copyList(books, BookVO.class);

            cacheService.setObject(CacheKey.INDEX_UPDATE_BOOK_KEY, result, 3600);
        }

        return result;
    }

    @Override
    public PageInfo searchByPage(BookSP params, int page, int pageSize) {
        if (null != params.getUpdatePeriod()) {
            long cur = System.currentTimeMillis();
            long period = params.getUpdatePeriod() * 24 * 3600 * 100;
            long time = cur - period;
            params.setUpdateTimeMin(new Date(time));
        }
        if (esEnable == 1) {
            try {
                return searchService.searchBook(params, page, pageSize);

            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
        PageHelper.startPage(page, pageSize);

        if (StringUtils.isNotBlank(params.getSort())) {
            OrderByHelper.orderBy(params.getSort() + " desc");
        }
        return new PageInfo(bookMapper.searchByPage(params));
    }

    @Override
    public List<BookCategory> listBookCategory() {
        List<BookCategory> result = (List<BookCategory>) cacheService.getObject(CacheKey.CATEGORY_LIST);
        if (null == result || result.isEmpty()) {
            SelectStatementProvider selectStatementProvider = select(id, name, workDirection, sort)
                    .from(bookCategory)
                    .orderBy(sort)
                    .build()
                    .render(RenderingStrategies.MYBATIS3);
            result = bookCategoryMapper.selectMany(selectStatementProvider);

            cacheService.setObject(CacheKey.CATEGORY_LIST, result);
        }
        return result;
    }

    @Override
    public Book queryBookDetail(Long bookId) {
        SelectStatementProvider selectStatementProvider = select(book.allColumns())
                .from(book)
                .where(id, isEqualTo(bookId))
                .build()
                .render(RenderingStrategies.MYBATIS3);
        Book book = bookMapper.selectMany(selectStatementProvider).get(0);
        return book;
    }

    @Override
    public List<BookIndex> queryIndexList(Long bId, String orderBy, Integer limit) {
        if (StringUtils.isNotBlank(orderBy)) {
            OrderByHelper.orderBy(orderBy);
        }

        if (null != limit) {
            PageHelper.startPage(1, limit);
        }
        SelectStatementProvider selectStatementProvider = select(bookIndex.allColumns())
                .from(bookIndex)
                .where(bookId, isEqualTo(bId))
                .build()
                .render(RenderingStrategies.MYBATIS3);
        return bookIndexMapper.selectMany(selectStatementProvider);
    }

    @Override
    public BookIndex queryBookIndex(Long bookIndexId) {
        SelectStatementProvider selectStatementProvider = select(bookIndex.allColumns())
                .from(bookIndex)
                .where(id, isEqualTo(bookIndexId))
                .build()
                .render(RenderingStrategies.MYBATIS3);
        return bookIndexMapper.selectMany(selectStatementProvider).get(0);
    }

    @Override
    public Long queryPreBookIndexId(Long bookId, Integer indexNum) {
        SelectStatementProvider selectStatementProvider = select(bookIndex.allColumns())
                .from(bookIndex)
                .where(BookIndexDynamicSqlSupport.bookId, isEqualTo(bookId))
                .and(BookIndexDynamicSqlSupport.indexNum, isLessThan(indexNum))
                .orderBy(BookIndexDynamicSqlSupport.indexNum.descending())
                .build()
                .render(RenderingStrategies.MYBATIS3);
        List<BookIndex> bookIndexList = bookIndexMapper.selectMany(selectStatementProvider);
        if (bookIndexList.isEmpty()) {
            return 0L;
        } else {
            return bookIndexList.get(0).getId();
        }
    }

    @Override
    public Long queryNextBookIndexId(Long bookId, Integer indexNum) {
        SelectStatementProvider selectStatementProvider = select(bookIndex.allColumns())
                .from(bookIndex)
                .where(BookIndexDynamicSqlSupport.bookId, isEqualTo(bookId))
                .and(BookIndexDynamicSqlSupport.indexNum, isGreaterThan(indexNum))
                .orderBy(BookIndexDynamicSqlSupport.indexNum.descending())
                .build()
                .render(RenderingStrategies.MYBATIS3);
        List<BookIndex> bookIndexList = bookIndexMapper.selectMany(selectStatementProvider);
        if (bookIndexList.isEmpty()) {
            return 0L;
        } else {
            return bookIndexList.get(0).getId();
        }
    }

    @Override
    public BookContent queryBookContent(Long bookIndexId) {
        SelectStatementProvider selectStatementProvider = select(bookContent.allColumns())
                .from(bookContent)
                .where(indexId, isEqualTo(bookIndexId))
                .build()
                .render(RenderingStrategies.MYBATIS3);
        return bookContentMapper.selectMany(selectStatementProvider).get(0);
    }

    @Override
    public List<Book> listRank(Byte type, Integer limit) {
        SortSpecification sortSpecification = visitCount.descending();
        switch (type) {
            case 1: {
                // 最新入库排序
                sortSpecification = createTime.descending();
                break;
            }
            case 2: {
                // 更新时间排序
                sortSpecification = lastIndexUpdateTime.descending();
                break;
            }
            case 3: {
                // 评论量排序
                sortSpecification = commentCount.descending();
                break;
            }
            default: {
                break;
            }
        }
        SelectStatementProvider selectStatement = select(id, catId, catName, bookName, lastIndexId, lastIndexName, authorId, authorName, picUrl, bookDesc, wordCount, lastIndexUpdateTime)
                .from(book)
                .where(wordCount, isGreaterThan(0))
                .orderBy(sortSpecification)
                .limit(limit)
                .build()
                .render(RenderingStrategies.MYBATIS3);
        return bookMapper.selectMany(selectStatement);
    }

    @Override
    public void addVisitCount(Long bookId, Integer visitCount) {
        bookMapper.addVisitCount(bookId, visitCount);
    }

    @Override
    public long queryIndexCount(Long bookId) {
        SelectStatementProvider selectStatementProvider = select(count(id))
                .from(bookIndex)
                .where(id, isEqualTo(bookId))
                .build()
                .render(RenderingStrategies.MYBATIS3);
        return bookMapper.count(selectStatementProvider);
    }

    @Override
    public List<Book> listRecBookByCatId(Integer id) {
        SelectStatementProvider selectStatementProvider = select(book.allColumns())
                .from(book)
                .where(catId, isEqualTo(id))
                .build()
                .render(RenderingStrategies.MYBATIS3);
        return bookMapper.selectMany(selectStatementProvider);
    }

    @Override
    public Long queryFirstBookIndexId(Long bookId) {
        SelectStatementProvider selectStatementProvider = select(bookIndex.allColumns())
                .from(bookIndex)
                .where(BookIndexDynamicSqlSupport.bookId, isEqualTo(bookId))
                .orderBy(BookIndexDynamicSqlSupport.indexNum.descending())
                .limit(1)
                .build()
                .render(RenderingStrategies.MYBATIS3);
        List<BookIndex> bookIndexList = bookIndexMapper.selectMany(selectStatementProvider);
        if (bookIndexList.isEmpty()) {
            return 0L;
        } else {
            return bookIndexList.get(0).getId();
        }
    }

    @Override
    public List<BookCommentVO> listCommentByPage(Long userId, Long bookId, int page, int pageSize) {
        PageHelper.startPage(page, pageSize);
        // todo: 试下可不可以直接再SQL写
        OrderByHelper.orderBy("t1.create_time desc");
        return bookCommentMapper.listCommentByPage(userId, bookId);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addBookComment(Long userId, BookComment comment) {
        // 先判断用户是否评论过
        SelectStatementProvider selectStatementProvider = select(bookComment.allColumns())
                .from(bookComment)
                .where(BookCommentDynamicSqlSupport.createUserId, isEqualTo(userId))
                .build()
                .render(RenderingStrategies.MYBATIS3);
        List<BookComment> bookComments = bookCommentMapper.selectMany(selectStatementProvider);
        if (bookComments.size() > 0) {
            throw new BusinessException(ResponseStatus.HAS_COMMENTS);
        }
        // 保存评价
        comment.setCreateUserId(userId);
        comment.setCreateTime(new Date());
        bookCommentMapper.insert(comment);

        // 增加书籍的评论数
        bookMapper.addCommentCount(comment.getBookId());
    }

    @Override
    public Long getOrCreateAuthorIdByName(String authorName, Byte workDirection) {
        Long authorId;
        SelectStatementProvider selectStatement = select(BookAuthorDynamicSqlSupport.id)
                .from(BookAuthorDynamicSqlSupport.bookAuthor)
                .where(BookAuthorDynamicSqlSupport.penName, isEqualTo(authorName))
                .build()
                .render(RenderingStrategies.MYBATIS3);
        List<BookAuthor> bookAuthors = bookAuthorMapper.selectMany(selectStatement);
        if (bookAuthors.size() > 0) {
            //作者存在
            authorId = bookAuthors.get(0).getId();
        } else {
            //作者不存在，先创建作者
            Date currentDate = new Date();
            authorId = new IdWorker().nextId();
            BookAuthor bookAuthor = new BookAuthor();
            bookAuthor.setId(authorId);
            bookAuthor.setPenName(authorName);
            bookAuthor.setWorkDirection(workDirection);
            bookAuthor.setStatus((byte) 1);
            bookAuthor.setCreateTime(currentDate);
            bookAuthor.setUpdateTime(currentDate);
            bookAuthorMapper.insertSelective(bookAuthor);
        }
        return authorId;
    }

    @Override
    public Long queryIdByNameAndAuthor(String bookName, String author) {
        //查询小说ID
        SelectStatementProvider selectStatement = select(id)
                .from(book)
                .where(BookDynamicSqlSupport.bookName, isEqualTo(bookName))
                .and(BookDynamicSqlSupport.authorName, isEqualTo(authorName))
                .build()
                .render(RenderingStrategies.MYBATIS3);
        List<Book> books = bookMapper.selectMany(selectStatement);
        if (books.size() > 0) {
            return books.get(0).getId();
        }
        return null;
    }

    @Override
    public List<Integer> queryIndexNumByBookId(Long bookId) {
        return null;
    }

    @Override
    public List<Book> queryNetworkPicBooks(String localPicPrefix, Integer limit) {
        return bookMapper.queryNetworkPicBooks(localPicPrefix, limit);
    }

    @Override
    public void updateBookPicToLocal(String picUrl, Long bookId) {
        picUrl = fileService.transPic(picUrl, picSavePath);

        bookMapper.update(update(book)
                .set(BookDynamicSqlSupport.picUrl)
                .equalTo(picUrl)
                .set(updateTime).equalTo(new Date())
                .where(id, isEqualTo(bookId))
                .build()
                .render(RenderingStrategies.MYBATIS3));
    }

    @Override
    public List<Book> listBookPageByUserId(Long userId, int page, int pageSize) {
        return null;
    }

    @Override
    public void addBook(Book book, Long authorId, String penName) {

    }

    @Override
    public void updateBookStatus(Long bookId, Byte status, Long authorId) {

    }

    @Override
    public void addBookContent(Long bookId, String indexName, String content, Long authorId) {

    }

    @Override
    public List<Book> queryBookByUpdateTimeByPage(Date startDate, int limit) {
        return bookMapper.selectMany(select(book.allColumns())
                .from(book)
                .where(updateTime, isGreaterThan(startDate))
                .orderBy(updateTime)
                .limit(limit)
                .build()
                .render(RenderingStrategies.MYBATIS3));
    }
}
